/**
 * Copyright (C) 2009 aileron.cc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cc.aileron.template.flow;

import static cc.aileron.template.flow.FlowCategory.DEF;
import static cc.aileron.template.flow.FlowCategory.EACH;
import static cc.aileron.template.flow.FlowCategory.EXECUTE;
import static cc.aileron.template.flow.FlowCategory.SEQUENTIAL;

import java.util.EnumMap;
import java.util.List;

import cc.aileron.accessor.PojoAccessor;
import cc.aileron.accessor.PojoAccessorFactory;
import cc.aileron.template.context.TemplateContext;
import cc.aileron.template.parser.ParserMethod;

import com.google.inject.Inject;

/**
 * 
 * flow-executor
 * 
 * @author Aileron
 *
 */
public class FlowExecutorImpl implements FlowExecutor
{
    public void call(
            final TemplateContext context)
    throws FlowMethodNotFoundError
    {
        this.call(context, root);
    }

    public FlowExecutor setComponnent(final FlowComponent component)
    {
        this.root = component;
        return this;
    }

    /**
     * @param context
     * @param component
     * @throws FlowMethodNotFoundError
     */
    void call(
            final TemplateContext context,
            final FlowComponent component)
    throws FlowMethodNotFoundError
    {
        final IF instance = map.get(component.category);
        if(instance==null)
        {
            throw new FlowMethodNotFoundError(component.category);
        }
        instance.call(component.self, context, component.children);
    }

    /**
     * constractor injection
     * @param factory
     */
    @Inject
    public FlowExecutorImpl(final PojoAccessorFactory factory)
    {
        map.put(EXECUTE, new Execute());
        map.put(DEF, new Def(this));
        map.put(EACH, new Each(this,factory));
        map.put(SEQUENTIAL, new Sequential(this));
    }

    private FlowComponent root;

    private final EnumMap<FlowCategory, IF> map = new EnumMap<FlowCategory, IF>(FlowCategory.class);
}

class Def implements IF
{
    public void call(final Object self,
            final TemplateContext context,
            final List<FlowComponent> children)
    {
        final FlowDefMethod method = (FlowDefMethod) self ;
        if(!method.call(context))
        {
            return ;
        }
        for(final FlowComponent component : children)
        {
            executor.call(context, component);
        }
    }

    public Def(final FlowExecutorImpl executor)
    {
        this.executor = executor;
    }

    private final FlowExecutorImpl executor;
}

class Each implements IF
{
    public void call(final Object self,
            final TemplateContext context,
            final List<FlowComponent> children)
    {
        final PojoAccessor<?> oldAccessor = context.getAccessor();
        final FlowEachMethod method = (FlowEachMethod) self;
        int count = 0;
        for(final Object pojo : method.call(context))
        {
            context.setAccessor(factory.create(pojo));
            for(final FlowComponent component : children)
            {
                executor.call(context, component);
            }
            count += 1;
        }
        context.setAccessor(oldAccessor);
    }

    public Each(final FlowExecutorImpl executor,final PojoAccessorFactory factory)
    {
        this.executor = executor;
        this.factory = factory;
    }

    private final PojoAccessorFactory factory;

    private final FlowExecutorImpl executor;
}

class Execute implements IF
{
    public void call(final Object self,
            final TemplateContext context,
            final List<FlowComponent> children)
    {
        final ParserMethod method = (ParserMethod) self ;
        method.call(context);
    }
}

interface IF
{
    /**
     * @param self
     * @param context
     * @param children
     */
    void call(Object self,TemplateContext context,List<FlowComponent> children);
}

class Sequential implements IF
{
    public void call(final Object self,
            final TemplateContext context,
            final List<FlowComponent> children)
    {
        for(final FlowComponent component : children)
        {
            executor.call(context, component);
        }
    }

    public Sequential(final FlowExecutorImpl executor)
    {
        this.executor = executor;
    }

    private final FlowExecutorImpl executor;
}